dash_charts.scatter_line_charts⚓︎
Charts for plotting scatter or fitted data.
View Source
"""Charts for plotting scatter or fitted data."""
import numpy as np
import plotly.graph_objects as go
from scipy import optimize
from .utils_fig import CustomChart, check_raw_data
def create_rolling_traces(df_raw, count_rolling, count_std):
"""Calculate traces for rolling average and standard deviation.
Args:
df_raw: pandas dataframe with columns `x: float`, `y: float` and `label: str`
count_rolling: number of points to use for the rolling calculation
count_std: number of standard deviations to use for the standard deviation
Returns:
list: of Scatter traces for rolling mean and std
"""
rolling_mean = df_raw['y'].rolling(count_rolling).mean().tolist()
rolling_std = df_raw['y'].rolling(count_std).std().tolist()
return [
go.Scatter(
fill='toself',
hoverinfo='skip',
name=f'{count_std}x STD Range',
opacity=0.5,
x=(df_raw['x'].tolist() + df_raw['x'].tolist()[::-1]),
y=(
np.add(rolling_mean, np.multiply(count_std, rolling_std)).tolist()
+ np.subtract(rolling_mean, np.multiply(count_std, rolling_std)).tolist()[::-1]
),
),
go.Scatter(
hoverinfo='skip',
mode='lines',
name='Rolling Mean',
opacity=0.9,
x=df_raw['x'],
y=rolling_mean,
),
]
def create_fit_traces(df_raw, name, fit_equation, suppress_fit_errors=False): # noqa: CCR001
"""Create traces for specified equation.
Args:
df_raw: pandas dataframe with columns `name: str`, `x: float`, `y: float` and `label: str`
name: unique name for trace
fit_equation: equation used
suppress_fit_errors: If True, bury errors from scipy fit. Default is False.
Returns:
list: of Scatter traces for fitted equation
"""
fitted_data = []
try:
popt, pcov = optimize.curve_fit(fit_equation, xdata=df_raw['x'], ydata=df_raw['y'], method='lm')
# Calculate representative x values for plotting fit
x_min = np.min(df_raw['x'])
x_max = np.max(df_raw['x'])
x_range = x_max - x_min
x_values = sorted([
x_min - 0.05 * x_range,
*np.divide(range(int(x_min * 10), int(x_max * 10)), 10),
x_max + 0.05 * x_range,
])
fitted_data = [
go.Scatter(
mode='lines+markers',
name=name,
opacity=0.9,
text=f'popt:{[round(param, 3) for param in popt]}',
x=x_values,
y=fit_equation(x_values, *popt),
),
]
except (RuntimeError, ValueError) as err: # pragma: no cover
if not suppress_fit_errors:
raise
return fitted_data # noqa: R504
class RollingChart(CustomChart):
"""Rolling Mean and Filled Standard Deviation Chart for monitoring trends."""
count_std = 5
"""Count of STD deviations to display. Default 5."""
count_rolling = count_std
"""Count of items to use for rolling calculations. Default `count_std`."""
label_data = 'Data'
"""Label for the scatter data. Default is 'Data'."""
def create_traces(self, df_raw):
"""Return traces for plotly chart.
Args:
df_raw: pandas dataframe with columns `x: float`, `y: float` and `label: str`
Returns:
list: Dash chart traces
"""
# Verify data format
check_raw_data(df_raw, ['x', 'y', 'label'])
# Create and return the traces
chart_data = [
go.Scatter(
mode='markers',
name=self.label_data,
opacity=0.5,
text=df_raw['label'],
x=df_raw['x'],
y=df_raw['y'],
),
]
# Only add the rolling calculations if there are a sufficient number of points
if len(df_raw['x']) >= self.count_rolling:
chart_data.extend(
create_rolling_traces(df_raw, self.count_rolling, self.count_std),
)
return chart_data
class FittedChart(CustomChart):
"""Scatter Chart with optional Fitted Lines."""
label_data = 'Data'
"""Label for the scatter data. Default is 'Data'."""
fit_eqs = []
"""List of fit equations."""
fallback_mode = 'lines+markers'
"""If not fit_eqs are specified, will fallback to `lines+markers`. Can be set to `markers`."""
min_scatter_for_fit = 0
"""List of fit equations."""
suppress_fit_errors = False
"""If True, bury errors from scipy fit and will print message to console. Default is True."""
def create_traces(self, df_raw): # noqa: CCR001
"""Return traces for plotly chart.
Args:
df_raw: pandas dataframe with columns `name: str`, `x: float`, `y: float` and `label: str`
Returns:
list: Dash chart traces
"""
# Verify data format
check_raw_data(df_raw, ['name', 'x', 'y', 'label'])
# Separate raw tidy dataframe into separate scatter plots
scatter_data = []
fit_traces = []
for name in set(df_raw['name']):
df_name = df_raw[df_raw['name'] == name]
scatter_data.append(
go.Scatter(
customdata=[name],
mode='markers' if self.fit_eqs else self.fallback_mode,
name=name,
opacity=0.5,
text=df_name['label'],
x=df_name['x'],
y=df_name['y'],
),
)
if len(df_name['x']) > self.min_scatter_for_fit:
for fit_name, fit_equation in self.fit_eqs:
fit_traces.extend(
create_fit_traces(df_name, f'{name}-{fit_name}', fit_equation, self.suppress_fit_errors),
)
return scatter_data + fit_traces
Functions⚓︎
create_fit_traces⚓︎
def create_fit_traces(
df_raw,
name,
fit_equation,
suppress_fit_errors=False
)
Create traces for specified equation.
Parameters:
| Name | Description |
|---|---|
| df_raw | pandas dataframe with columns name: str, x: float, y: float and label: str |
| name | unique name for trace |
| fit_equation | equation used |
| suppress_fit_errors | If True, bury errors from scipy fit. Default is False. |
Returns:
| Type | Description |
|---|---|
| list | of Scatter traces for fitted equation |
View Source
def create_fit_traces(df_raw, name, fit_equation, suppress_fit_errors=False): # noqa: CCR001
"""Create traces for specified equation.
Args:
df_raw: pandas dataframe with columns `name: str`, `x: float`, `y: float` and `label: str`
name: unique name for trace
fit_equation: equation used
suppress_fit_errors: If True, bury errors from scipy fit. Default is False.
Returns:
list: of Scatter traces for fitted equation
"""
fitted_data = []
try:
popt, pcov = optimize.curve_fit(fit_equation, xdata=df_raw['x'], ydata=df_raw['y'], method='lm')
# Calculate representative x values for plotting fit
x_min = np.min(df_raw['x'])
x_max = np.max(df_raw['x'])
x_range = x_max - x_min
x_values = sorted([
x_min - 0.05 * x_range,
*np.divide(range(int(x_min * 10), int(x_max * 10)), 10),
x_max + 0.05 * x_range,
])
fitted_data = [
go.Scatter(
mode='lines+markers',
name=name,
opacity=0.9,
text=f'popt:{[round(param, 3) for param in popt]}',
x=x_values,
y=fit_equation(x_values, *popt),
),
]
except (RuntimeError, ValueError) as err: # pragma: no cover
if not suppress_fit_errors:
raise
return fitted_data # noqa: R504
create_rolling_traces⚓︎
def create_rolling_traces(
df_raw,
count_rolling,
count_std
)
Calculate traces for rolling average and standard deviation.
Parameters:
| Name | Description |
|---|---|
| df_raw | pandas dataframe with columns x: float, y: float and label: str |
| count_rolling | number of points to use for the rolling calculation |
| count_std | number of standard deviations to use for the standard deviation |
Returns:
| Type | Description |
|---|---|
| list | of Scatter traces for rolling mean and std |
View Source
def create_rolling_traces(df_raw, count_rolling, count_std):
"""Calculate traces for rolling average and standard deviation.
Args:
df_raw: pandas dataframe with columns `x: float`, `y: float` and `label: str`
count_rolling: number of points to use for the rolling calculation
count_std: number of standard deviations to use for the standard deviation
Returns:
list: of Scatter traces for rolling mean and std
"""
rolling_mean = df_raw['y'].rolling(count_rolling).mean().tolist()
rolling_std = df_raw['y'].rolling(count_std).std().tolist()
return [
go.Scatter(
fill='toself',
hoverinfo='skip',
name=f'{count_std}x STD Range',
opacity=0.5,
x=(df_raw['x'].tolist() + df_raw['x'].tolist()[::-1]),
y=(
np.add(rolling_mean, np.multiply(count_std, rolling_std)).tolist()
+ np.subtract(rolling_mean, np.multiply(count_std, rolling_std)).tolist()[::-1]
),
),
go.Scatter(
hoverinfo='skip',
mode='lines',
name='Rolling Mean',
opacity=0.9,
x=df_raw['x'],
y=rolling_mean,
),
]
Classes⚓︎
FittedChart⚓︎
class FittedChart(
*,
title,
xlabel,
ylabel,
layout_overrides=()
)
View Source
class FittedChart(CustomChart):
"""Scatter Chart with optional Fitted Lines."""
label_data = 'Data'
"""Label for the scatter data. Default is 'Data'."""
fit_eqs = []
"""List of fit equations."""
fallback_mode = 'lines+markers'
"""If not fit_eqs are specified, will fallback to `lines+markers`. Can be set to `markers`."""
min_scatter_for_fit = 0
"""List of fit equations."""
suppress_fit_errors = False
"""If True, bury errors from scipy fit and will print message to console. Default is True."""
def create_traces(self, df_raw): # noqa: CCR001
"""Return traces for plotly chart.
Args:
df_raw: pandas dataframe with columns `name: str`, `x: float`, `y: float` and `label: str`
Returns:
list: Dash chart traces
"""
# Verify data format
check_raw_data(df_raw, ['name', 'x', 'y', 'label'])
# Separate raw tidy dataframe into separate scatter plots
scatter_data = []
fit_traces = []
for name in set(df_raw['name']):
df_name = df_raw[df_raw['name'] == name]
scatter_data.append(
go.Scatter(
customdata=[name],
mode='markers' if self.fit_eqs else self.fallback_mode,
name=name,
opacity=0.5,
text=df_name['label'],
x=df_name['x'],
y=df_name['y'],
),
)
if len(df_name['x']) > self.min_scatter_for_fit:
for fit_name, fit_equation in self.fit_eqs:
fit_traces.extend(
create_fit_traces(df_name, f'{name}-{fit_name}', fit_equation, self.suppress_fit_errors),
)
return scatter_data + fit_traces
Ancestors (in MRO)⚓︎
- dash_charts.utils_fig.CustomChart
Class variables⚓︎
annotations
fallback_mode
If not fit_eqs are specified, will fallback to lines+markers. Can be set to markers.
fit_eqs
List of fit equations.
label_data
Label for the scatter data. Default is ‘Data’.
min_scatter_for_fit
List of fit equations.
suppress_fit_errors
If True, bury errors from scipy fit and will print message to console. Default is True.
Instance variables⚓︎
axis_range
Specify x/y axis range or leave as empty dictionary for autorange.
Methods⚓︎
apply_custom_layout⚓︎
def apply_custom_layout(
self,
layout
)
Extend and/or override layout with custom settings.
Parameters:
| Name | Description |
|---|---|
| layout | base layout dictionary. Typically from self.create_layout() |
Returns:
| Type | Description |
|---|---|
| dict | layout for Dash figure |
View Source
def apply_custom_layout(self, layout):
"""Extend and/or override layout with custom settings.
Args:
layout: base layout dictionary. Typically from self.create_layout()
Returns:
dict: layout for Dash figure
"""
for parent_key, sub_key, value in self.layout_overrides:
if sub_key is not None:
layout[parent_key][sub_key] = value
else:
layout[parent_key] = value
return layout
create_figure⚓︎
def create_figure(
self,
df_raw,
**kwargs_data
)
Create the figure dictionary.
Parameters:
| Name | Description |
|---|---|
| df_raw | data to pass to formatter method |
| kwargs_data | keyword arguments to pass to the data formatter method |
Returns:
| Type | Description |
|---|---|
| dict | keys data and layout for Dash |
View Source
def create_figure(self, df_raw, **kwargs_data):
"""Create the figure dictionary.
Args:
df_raw: data to pass to formatter method
kwargs_data: keyword arguments to pass to the data formatter method
Returns:
dict: keys `data` and `layout` for Dash
"""
return {
'data': self.create_traces(df_raw, **kwargs_data),
'layout': go.Layout(self.apply_custom_layout(self.create_layout())),
}
create_layout⚓︎
def create_layout(
self
)
Return the standard layout. Can be overridden and modified when inherited.
Returns:
| Type | Description |
|---|---|
| dict | layout for Dash figure |
View Source
def create_layout(self):
"""Return the standard layout. Can be overridden and modified when inherited.
Returns:
dict: layout for Dash figure
"""
layout = {
'annotations': self.annotations,
'title': go.layout.Title(text=self.title),
'xaxis': {
'automargin': True,
'title': self.labels['x'],
},
'yaxis': {
'automargin': True,
'title': self.labels['y'],
'zeroline': True,
},
'legend': {'orientation': 'h', 'y': -0.25}, # below XAxis label
'hovermode': 'closest',
}
# Optionally apply the specified range
for axis in ['x', 'y']:
axis_name = f'{axis}axis'
if axis in self.axis_range:
layout[axis_name]['range'] = self.axis_range[axis]
else:
layout[axis_name]['autorange'] = True
return layout
create_traces⚓︎
def create_traces(
self,
df_raw
)
Return traces for plotly chart.
Parameters:
| Name | Description |
|---|---|
| df_raw | pandas dataframe with columns name: str, x: float, y: float and label: str |
Returns:
| Type | Description |
|---|---|
| list | Dash chart traces |
View Source
def create_traces(self, df_raw): # noqa: CCR001
"""Return traces for plotly chart.
Args:
df_raw: pandas dataframe with columns `name: str`, `x: float`, `y: float` and `label: str`
Returns:
list: Dash chart traces
"""
# Verify data format
check_raw_data(df_raw, ['name', 'x', 'y', 'label'])
# Separate raw tidy dataframe into separate scatter plots
scatter_data = []
fit_traces = []
for name in set(df_raw['name']):
df_name = df_raw[df_raw['name'] == name]
scatter_data.append(
go.Scatter(
customdata=[name],
mode='markers' if self.fit_eqs else self.fallback_mode,
name=name,
opacity=0.5,
text=df_name['label'],
x=df_name['x'],
y=df_name['y'],
),
)
if len(df_name['x']) > self.min_scatter_for_fit:
for fit_name, fit_equation in self.fit_eqs:
fit_traces.extend(
create_fit_traces(df_name, f'{name}-{fit_name}', fit_equation, self.suppress_fit_errors),
)
return scatter_data + fit_traces
initialize_mutables⚓︎
def initialize_mutables(
self
)
Initialize the mutable data members to prevent modifying one attribute and impacting all instances.
View Source
def initialize_mutables(self):
"""Initialize the mutable data members to prevent modifying one attribute and impacting all instances."""
...
RollingChart⚓︎
class RollingChart(
*,
title,
xlabel,
ylabel,
layout_overrides=()
)
View Source
class RollingChart(CustomChart):
"""Rolling Mean and Filled Standard Deviation Chart for monitoring trends."""
count_std = 5
"""Count of STD deviations to display. Default 5."""
count_rolling = count_std
"""Count of items to use for rolling calculations. Default `count_std`."""
label_data = 'Data'
"""Label for the scatter data. Default is 'Data'."""
def create_traces(self, df_raw):
"""Return traces for plotly chart.
Args:
df_raw: pandas dataframe with columns `x: float`, `y: float` and `label: str`
Returns:
list: Dash chart traces
"""
# Verify data format
check_raw_data(df_raw, ['x', 'y', 'label'])
# Create and return the traces
chart_data = [
go.Scatter(
mode='markers',
name=self.label_data,
opacity=0.5,
text=df_raw['label'],
x=df_raw['x'],
y=df_raw['y'],
),
]
# Only add the rolling calculations if there are a sufficient number of points
if len(df_raw['x']) >= self.count_rolling:
chart_data.extend(
create_rolling_traces(df_raw, self.count_rolling, self.count_std),
)
return chart_data
Ancestors (in MRO)⚓︎
- dash_charts.utils_fig.CustomChart
Class variables⚓︎
annotations
count_rolling
Count of items to use for rolling calculations. Default count_std.
count_std
Count of STD deviations to display. Default 5.
label_data
Label for the scatter data. Default is ‘Data’.
Instance variables⚓︎
axis_range
Specify x/y axis range or leave as empty dictionary for autorange.
Methods⚓︎
apply_custom_layout⚓︎
def apply_custom_layout(
self,
layout
)
Extend and/or override layout with custom settings.
Parameters:
| Name | Description |
|---|---|
| layout | base layout dictionary. Typically from self.create_layout() |
Returns:
| Type | Description |
|---|---|
| dict | layout for Dash figure |
View Source
def apply_custom_layout(self, layout):
"""Extend and/or override layout with custom settings.
Args:
layout: base layout dictionary. Typically from self.create_layout()
Returns:
dict: layout for Dash figure
"""
for parent_key, sub_key, value in self.layout_overrides:
if sub_key is not None:
layout[parent_key][sub_key] = value
else:
layout[parent_key] = value
return layout
create_figure⚓︎
def create_figure(
self,
df_raw,
**kwargs_data
)
Create the figure dictionary.
Parameters:
| Name | Description |
|---|---|
| df_raw | data to pass to formatter method |
| kwargs_data | keyword arguments to pass to the data formatter method |
Returns:
| Type | Description |
|---|---|
| dict | keys data and layout for Dash |
View Source
def create_figure(self, df_raw, **kwargs_data):
"""Create the figure dictionary.
Args:
df_raw: data to pass to formatter method
kwargs_data: keyword arguments to pass to the data formatter method
Returns:
dict: keys `data` and `layout` for Dash
"""
return {
'data': self.create_traces(df_raw, **kwargs_data),
'layout': go.Layout(self.apply_custom_layout(self.create_layout())),
}
create_layout⚓︎
def create_layout(
self
)
Return the standard layout. Can be overridden and modified when inherited.
Returns:
| Type | Description |
|---|---|
| dict | layout for Dash figure |
View Source
def create_layout(self):
"""Return the standard layout. Can be overridden and modified when inherited.
Returns:
dict: layout for Dash figure
"""
layout = {
'annotations': self.annotations,
'title': go.layout.Title(text=self.title),
'xaxis': {
'automargin': True,
'title': self.labels['x'],
},
'yaxis': {
'automargin': True,
'title': self.labels['y'],
'zeroline': True,
},
'legend': {'orientation': 'h', 'y': -0.25}, # below XAxis label
'hovermode': 'closest',
}
# Optionally apply the specified range
for axis in ['x', 'y']:
axis_name = f'{axis}axis'
if axis in self.axis_range:
layout[axis_name]['range'] = self.axis_range[axis]
else:
layout[axis_name]['autorange'] = True
return layout
create_traces⚓︎
def create_traces(
self,
df_raw
)
Return traces for plotly chart.
Parameters:
| Name | Description |
|---|---|
| df_raw | pandas dataframe with columns x: float, y: float and label: str |
Returns:
| Type | Description |
|---|---|
| list | Dash chart traces |
View Source
def create_traces(self, df_raw):
"""Return traces for plotly chart.
Args:
df_raw: pandas dataframe with columns `x: float`, `y: float` and `label: str`
Returns:
list: Dash chart traces
"""
# Verify data format
check_raw_data(df_raw, ['x', 'y', 'label'])
# Create and return the traces
chart_data = [
go.Scatter(
mode='markers',
name=self.label_data,
opacity=0.5,
text=df_raw['label'],
x=df_raw['x'],
y=df_raw['y'],
),
]
# Only add the rolling calculations if there are a sufficient number of points
if len(df_raw['x']) >= self.count_rolling:
chart_data.extend(
create_rolling_traces(df_raw, self.count_rolling, self.count_std),
)
return chart_data
initialize_mutables⚓︎
def initialize_mutables(
self
)
Initialize the mutable data members to prevent modifying one attribute and impacting all instances.
View Source
def initialize_mutables(self):
"""Initialize the mutable data members to prevent modifying one attribute and impacting all instances."""
...
Created: August 5, 2022